/*
 * This file is part of ELCube.
 *
 * ELCube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ELCube is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ELCube.  If not, see <https://www.gnu.org/licenses/>.
 */
package cn.nkpro.elcube.components.financial.cards

import cn.nkpro.elcube.annotation.NkNote
import cn.nkpro.elcube.co.spel.NkSpELManager
import cn.nkpro.elcube.docengine.NkAbstractCard
import cn.nkpro.elcube.docengine.gen.FinVoucherIExample
import cn.nkpro.elcube.docengine.gen.FinVoucherIMapper
import cn.nkpro.elcube.docengine.model.DocDefIV
import cn.nkpro.elcube.docengine.model.DocHV
import cn.nkpro.elcube.financial.model.FinVoucherAssist
import cn.nkpro.elcube.financial.model.FinVoucherIV
import cn.nkpro.elcube.utils.DateTimeUtilz
import com.alibaba.fastjson.JSON
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.springframework.util.Assert

import java.util.stream.Collectors


/**
 *
 * 查询科目金额
 * SELECT TARGET_DOC_ID 单据,
 *        BUSINESS_KEY 业务分类,
 *        VOUCHER_SUBJECT 科目,
 *        IFNULL(SUM(CREDIT_AMOUNT), 0) 贷方金额,
 *        IFNULL(SUM(DEBIT_AMOUNT) , 0) 借方金额
 *   FROM NK_FIN_VOUCHER_I
 *  WHERE STATE = 1
 *    AND TARGET_DOC_ID = 'ff808081-8021da4e-0180-21da4e65-0000'
 *  GROUP BY BUSINESS_KEY,VOUCHER_SUBJECT,TARGET_DOC_ID
 */
@Order(100014)
@NkNote("财务凭证引擎")
@Component("NkCardAccountingVoucher")
class NkCardAccountingVoucher extends NkAbstractCard<Data,Def> {

    @Autowired
    private NkSpELManager spELManager
    @Autowired
    private FinVoucherIMapper voucherIMapper

    @Override
    boolean independentStore() {
        return true
    }

    @Override
    Data calculate(DocHV doc, Data data, DocDefIV defIV, Def d, boolean isTrigger, Object options) {

        def context = spELManager.createContext(doc)
        data = new Data()
        data.active = d.activeSpEL&&spELManager.invoke(d.activeSpEL,context) as boolean

        if(data.active){
            Assert.notEmpty(d.items,"账目明细不能为空")

            doc.docName = d.voucherKind

            if(d.vars){
                d.vars.forEach{var->
                    if(var.key&&var.valueSpEL){
                        context.setVariable(
                                var.key,
                                spELManager.invoke(var.valueSpEL,context)
                        )
                    }
                }
            }

            def accountDate   = d.accountDateSpEL?spELManager.invoke(d.accountDateSpEL,context) as String:null
            def accountYear   = d.accountYearSpEL?spELManager.invoke(d.accountYearSpEL,context) as String:null
            def accountPeriod = d.accountPeriodSpEL?spELManager.invoke(d.accountPeriodSpEL,context) as String:null
            def nowSeconds    = DateTimeUtilz.nowSeconds()



            int index = 1
            data.items = new ArrayList()
            d.items.findAll {i->i.amountSpEL}.forEach {itemDef ->

                def dataSource = itemDef.dataSourceSpEL ? spELManager.invoke(itemDef.dataSourceSpEL, context) : null
                if(!(dataSource instanceof List)){
                    dataSource = Collections.singletonList(dataSource)
                }
                List list = dataSource as List

                list.forEach { dsItem ->

                    if(dsItem)
                        context.setVariable("item",dsItem)

                    def amount      = spELManager.invoke(itemDef.amountSpEL, context) as Double

                    // 如果金额为null，则不生成该条目
                    if(amount == null)
                        return

                    def targetDocId = itemDef.targetDocIdSpEL ? spELManager.invoke(itemDef.targetDocIdSpEL, context) as String: ''
                    def explanation = itemDef.explanationSpEL ? spELManager.invoke(itemDef.explanationSpEL, context) as String: ''
                    def cashFlow    = itemDef.cashFlowSpEL ? spELManager.invoke(itemDef.cashFlowSpEL, context) as String: ''
                    def cashFlowSide = itemDef.cashFlowSpEL ? itemDef.cashFlowSide : null

                    FinVoucherIV item = new FinVoucherIV()
                    data.items.add(item)

                    item.docId              = doc.docId
                    item.cardKey            = defIV.cardKey
                    item.itemIndex          = index++
                    item.targetDocId        = targetDocId

                    item.businessKey        = itemDef.businessKey ?: itemDef.subject

                    item.voucherKind        = d.voucherKind
                    item.voucherSubject     = itemDef.subject
                    item.voucherSubjectDesc = itemDef.subjectDesc

                    item.accountDate        = accountDate
                    item.accountYear        = accountYear
                    item.accountPeriod      = accountPeriod

                    item.currType           = itemDef.currType
                    item.debitAmount        = StringUtils.containsAny(itemDef.category,'借','D')?amount:null
                    item.creditAmount       = StringUtils.containsAny(itemDef.category,'贷','C')?amount:null
                    item.explanation        = explanation

                    item.cashFlow           = cashFlow
                    item.cashFlowSide       = cashFlowSide

                    item.updatedTime        = nowSeconds

                    if(itemDef.assists){
                        item.assists = itemDef.assists.stream().map({ ass ->
                            FinVoucherAssist assist = new FinVoucherAssist()
                            assist.key   = ass.key
                            assist.value = ass.valueSpEL?(spELManager.invoke(ass.valueSpEL, context)?:'') as String:''
                            return assist
                        }).collect(Collectors.toList())
                    }

                }
            }
        }
        this.afterGetData(doc, data, defIV, d)
        return super.calculate(doc, data, defIV, d, isTrigger, options) as Data
    }

    @Override
    Data beforeGetData(DocHV doc, DocDefIV defIV, Def d) {

        FinVoucherIExample example = new FinVoucherIExample()
        example.createCriteria()
                .andDocIdEqualTo(doc.docId)
                .andCardKeyEqualTo(defIV.cardKey)
        example.orderByClause = 'ITEM_INDEX asc'

        voucherIMapper.selectByExample(example)

        Data data = new Data()
        data.items = (voucherIMapper.selectByExample(example).stream()
                .map { item ->
                    FinVoucherIV iv = new FinVoucherIV()

                    iv.docId = item.docId
                    iv.cardKey = item.cardKey
                    iv.itemIndex = item.itemIndex
                    iv.targetDocId = item.targetDocId

                    iv.voucherKind = item.voucherKind
                    iv.voucherSubject = item.voucherSubject
                    iv.voucherSubjectDesc = item.voucherSubjectDesc

                    iv.accountDate = item.accountDate
                    iv.accountYear = item.accountYear
                    iv.accountPeriod = item.accountPeriod

                    iv.currType = item.currType
                    iv.debitAmount = item.debitAmount
                    iv.creditAmount = item.creditAmount
                    iv.explanation = item.explanation

                    iv.cashFlow = item.cashFlow
                    iv.cashFlowSide = item.cashFlowSide

                    iv.updatedTime = item.updatedTime

                    iv.assists = item.extendedData ? JSON.parseArray(item.extendedData, FinVoucherAssist.class) : null

                    return iv
                }.collect(Collectors.toList())) as List<FinVoucherIV>

        return data
    }

    @Override
    Data afterGetData(DocHV doc, Data data, DocDefIV defIV, Def d) {
        if(data.active){
            def find = data.items.find()
            if(find){
                data.voucherKind = find.voucherKind
                data.accountDate = find.accountDate
                data.accountYear = find.accountYear
                data.accountPeriod = find.accountPeriod
            }
        }
        return super.afterGetData(doc, data, defIV, d) as Data
    }

    @Override
    Data beforeUpdate(DocHV doc, Data data, Data original, DocDefIV defIV, Def d) {

        def context = spELManager.createContext(doc)
        def state = d.effectiveSpEL&&spELManager.invoke(d.effectiveSpEL,context)?1:0

        FinVoucherIExample example = new FinVoucherIExample()
        example.createCriteria()
                .andDocIdEqualTo(doc.docId)
                .andCardKeyEqualTo(defIV.cardKey)
        voucherIMapper.deleteByExample(example)

        if(data.active && data.items){
            data.items.forEach{ item ->
                item.state        = state
                item.extendedData = item.assists ? JSON.toJSONString(item.assists) : null
                voucherIMapper.insert(item)
            }
        }

        return super.beforeUpdate(doc, data, original, defIV, d) as Data
    }

    @SuppressWarnings("unused")
    static class Data implements Serializable{
        Boolean active
        String voucherKind
        String accountDate
        String accountYear
        String accountPeriod
        List<FinVoucherIV> items
    }

    static class Def implements Serializable{
        String activeSpEL
        String effectiveSpEL
        String voucherKind
        String accountDateSpEL
        String accountYearSpEL
        String accountPeriodSpEL
        List<VarDef> vars
        List<ItemDef> items
    }
    static class VarDef implements Serializable{
        String key
        String valueSpEL
        @SuppressWarnings("unused")
        String remark
    }
    static class ItemDef implements Serializable{
        String businessKey
        String subject
        String subjectDesc
        String currType
        String category
        String dataSourceSpEL
        String targetDocIdSpEL
        String amountSpEL
        String explanationSpEL
        String cashFlowSpEL
        Integer cashFlowSide
        List<DetailAssistDef> assists
    }
    static class DetailAssistDef implements Serializable{
        String key
        String valueSpEL
    }
}
